﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using NetFrameworkExtensions;

namespace SoftwareEngineering.Modeling
{
  public static class ModelValidator
  {
    public static readonly object ContextRoot = new object();
    public static readonly object ContextPath = new object();

    public static bool TryValidate(this ModelBase model, ValidationContext context, ICollection<ValidationResult> errors)
    {
      if (model == null) throw new ArgumentNullException("model");
      if (errors == null) throw new ArgumentNullException("errors");

      var items = new Dictionary<object, object>
      {
        { ContextRoot, model }, 
        { ContextPath, Enumerable.Empty<Tup<PropertyInfo, ModelBase>>() }
      };

      if (context == null) 
        context = new ValidationContext(model, null, items);
      else
      {
        if (context.Items != null)
          foreach (var pair in context.Items)
            items.Add(pair.Key, pair.Value);
        context = new ValidationContext(model, context.ServiceContainer, new Dictionary<object, object>());
      }

      var result = model.TryValidateInternal(context, errors);

      return result;
    }

    internal static bool TryValidateInternal(this ModelBase model, ValidationContext context, ICollection<ValidationResult> errors)
    {
      if (model == null) throw new ArgumentNullException("model");
      if (errors == null) throw new ArgumentNullException("errors");

      var modelErrors = new List<ValidationResult>();
      var result = Validator.TryValidateObject(model, context, modelErrors);
      ModelBase modelRoot;
      IEnumerable<PropertyInfo> pathToModel;

      foreach (var error in modelErrors)
      {
        if (error is ModelValidationResult)
          errors.Add(error);
        else
        {
          var modelError = new ModelValidationResult(error, context);
          errors.Add(modelError);
        }
      }

      return result;
    }

    internal static ValidationContext CreateSubContext(this ValidationContext context, PropertyInfo property, ModelBase child)
    {
      if (property == null) throw new ArgumentNullException("property");
      if (child == null) throw new ArgumentNullException("child");
      if (context == null) return null;
      if (context.Items == null) return context;

      object value;
      if (context.Items.TryGetValue(ModelValidator.ContextPath, out value))
      {
        // modelRoot for sub-context is the same as in context
        var pathToModel = Seq.Build((IEnumerable<Tup<PropertyInfo, ModelBase>>)value, Tup.Create(property, child)).ToArray();
        var newItems = new Dictionary<object, object>(context.Items);
        newItems[ModelValidator.ContextPath] = pathToModel;
        return new ValidationContext(child, context.ServiceContainer, newItems);
      }
      return context;
    }

    public static string PrintUnlocalized(this ValidationResult result)
    {
      if (result is ModelValidationResult)
        return result.ToString();
      else if (result != null)
        if (result.MemberNames != null && result.MemberNames.Any())
          return string.Format("{0} on members {1}.", result.ErrorMessage, result.MemberNames.ToStringList(", ", m => "\"" + m + "\""));
        else
          return result.ErrorMessage;
      return string.Empty;
    }
  }
}
